﻿using System;
using System.Linq;
using MLSharp.Classification;

namespace MLSharp
{
	/// <summary>
	/// Extension methods for <see cref="IDataSet"/>.
	/// </summary>
	public static class DataSetExtensions
	{
		/// <summary>
		/// Creates a clone of the data set.
		/// </summary>
		/// <param name="dataSet"></param>
		/// <returns>A clone of the data set.</returns>
		public static IDataSet Clone(this IDataSet dataSet)
		{
			BasicDataSet ds = new BasicDataSet();

			foreach (DataAttribute attribute in dataSet.Attributes)
			{
				ds.Attributes.Add(attribute.Clone());
			}

			foreach (Instance instance in dataSet.Instances)
			{
				Instance copy = new Instance(instance.Label, (double[])instance.Values.Clone());
				ds.Instances.Add(copy);
			}

			ds.TargetAttributeIndex = dataSet.TargetAttributeIndex;

			return ds;
		}

		/// <summary>
		/// Verifies that all attributes in the dataset are numeric.
		/// </summary>
		/// <param name="dataSet"></param>
		public static void AssertNumeric(this IDataSet dataSet)
		{
			for (int i = 0; i < dataSet.Instances.Count; i++)
			{
				for (int j = 0; j < dataSet.Attributes.Count; j++)
				{
					if (!dataSet.Attributes[j].IsNumeric())
					{
						string message = string.Format("Attribute '{0}' (index {1}) is not numeric.",
						                               dataSet.Attributes[j].Name, j);

						throw new InvalidOperationException(message);
					}

					if (double.IsNaN(dataSet.Instances[i].Values[j]))
					{
						string message = string.Format("Value '{0}' in position {1}, instance {2} is missing.",
													   dataSet.Instances[i].Values[j], j, i + 1);

						throw new InvalidOperationException(message);
					}
				}
			}
		}

		/// <summary>
		/// Counts the number of instances in the data set that are positives.
		/// </summary>
		/// <param name="dataSet"></param>
		/// <returns>The number of instances that are positives.</returns>
		public static int CountPositiveInstances(this IDataSet dataSet)
		{
			if (dataSet.Attributes[dataSet.TargetAttributeIndex].Type != AttributeType.Set ||
				dataSet.Attributes[dataSet.TargetAttributeIndex].PossibleValues.Length != 2)
			{
				throw new NotSupportedException("This method is only valid for data sets with binary targets.");
			}

			//Instances store indecies into the possible values for Set attributes, so positive instances
			//should have a value of 0.
			int count =
				dataSet.Instances.Count(
					i => i.Values[dataSet.TargetAttributeIndex] == 0);

			return count;
		}

		/// <summary>
		/// Counts the number of correct classifications.
		/// </summary>
		/// <param name="dataSet">The data set.</param>
		/// <param name="results">The results.</param>
		/// <returns>The number of correct classifications in results.</returns>
		/// <remarks>
		/// The results must be in the same order as the instances, and the results array
		/// must be the same size as the data set's instances collection.
		/// </remarks>
		public static int CountCorrectClassifications(this IDataSet dataSet, ClassificationResult[] results)
		{
			if (results.Length != dataSet.Instances.Count)
			{
				throw new ArgumentException("Length of results does not match the number of instances in the data set.");
			}

			int correct = 0;

			for (int i = 0; i < results.Length; i++)
			{
				if (results[i].PredictedClass == dataSet.Instances[i].ClassValue)
				{
					correct++;
				}
			}

			return correct;
		}

		/// <summary>
		/// Moves all instances from the current dataset to the target dataset.
		/// </summary>
		/// <param name="currentDataSet"></param>
		/// <param name="targetDataSet"></param>
		public static void MoveInstancesTo(this IDataSet currentDataSet, IDataSet targetDataSet)
		{
			bool attributesCompatible = true;

			if (currentDataSet.Attributes.Count != targetDataSet.Attributes.Count)
			{
				attributesCompatible = false;
			}
			else
			{
				for (int i = 0; i < currentDataSet.Attributes.Count; i++)
				{
					DataAttribute sourceAttribute = currentDataSet.Attributes[i];
					DataAttribute destinationAttribute = targetDataSet.Attributes[i];

					if (sourceAttribute.Name != destinationAttribute.Name ||
						sourceAttribute.Type != destinationAttribute.Type ||
						sourceAttribute.PossibleValues != destinationAttribute.PossibleValues)
					{
						attributesCompatible = false;
						break;
					}
				}
			}

			if (!attributesCompatible)
			{
				throw new InvalidOperationException(
					"Attribute definitions are not compatible, be sure that all attribute types, names, and possible values match exactly.");
			}

			while (currentDataSet.Instances.Count > 0)
			{
				Instance instance = currentDataSet.Instances[0];

				currentDataSet.Instances.RemoveAt(0);

				targetDataSet.Instances.Add(instance);
			}
		}
	}
}
